'use strict'

const expect = require('chai').expect
const sinon = require('sinon')
const AwsProvider = require('../../../../../../../../../lib/plugins/aws/provider')
const AwsCompileWebsocketsEvents = require('../../../../../../../../../lib/plugins/aws/package/compile/events/websockets/index')
const Serverless = require('../../../../../../../../../lib/serverless')
const runServerless = require('../../../../../../../../utils/run-serverless')

describe('AwsCompileWebsocketsEvents', () => {
  let awsCompileWebsocketsEvents

  beforeEach(() => {
    const serverless = new Serverless({ commands: [], options: {} })
    serverless.service.environment = {
      vars: {},
      stages: {
        dev: {
          vars: {},
          regions: {
            'us-east-1': {
              vars: {},
            },
          },
        },
      },
    }
    const options = {
      stage: 'dev',
      region: 'us-east-1',
    }
    serverless.setProvider('aws', new AwsProvider(serverless, options))
    awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(
      serverless,
      options,
    )
  })

  describe('#constructor()', () => {
    let compileApiStub
    let compileIntegrationsStub
    let compileAuthorizersStub
    let compilePermissionsStub
    let compileRoutesStub
    let compileDeploymentStub
    let compileStageStub

    beforeEach(() => {
      compileApiStub = sinon
        .stub(awsCompileWebsocketsEvents, 'compileApi')
        .resolves()
      compileIntegrationsStub = sinon
        .stub(awsCompileWebsocketsEvents, 'compileIntegrations')
        .resolves()
      compileAuthorizersStub = sinon
        .stub(awsCompileWebsocketsEvents, 'compileAuthorizers')
        .resolves()
      compilePermissionsStub = sinon
        .stub(awsCompileWebsocketsEvents, 'compilePermissions')
        .resolves()
      compileRoutesStub = sinon
        .stub(awsCompileWebsocketsEvents, 'compileRoutes')
        .resolves()
      compileDeploymentStub = sinon
        .stub(awsCompileWebsocketsEvents, 'compileDeployment')
        .resolves()
      compileStageStub = sinon
        .stub(awsCompileWebsocketsEvents, 'compileStage')
        .resolves()
    })

    afterEach(() => {
      awsCompileWebsocketsEvents.compileApi.restore()
      awsCompileWebsocketsEvents.compileIntegrations.restore()
      awsCompileWebsocketsEvents.compileAuthorizers.restore()
      awsCompileWebsocketsEvents.compilePermissions.restore()
      awsCompileWebsocketsEvents.compileRoutes.restore()
      awsCompileWebsocketsEvents.compileDeployment.restore()
      awsCompileWebsocketsEvents.compileStage.restore()
    })

    it('should have hooks', () =>
      expect(awsCompileWebsocketsEvents.hooks).to.be.not.empty)

    it('should set the provider variable to be an instanceof AwsProvider', () =>
      expect(awsCompileWebsocketsEvents.provider).to.be.instanceof(AwsProvider))

    describe('"package:compileEvents" promise chain', () => {
      afterEach(() => {
        awsCompileWebsocketsEvents.validate.restore()
      })

      it('should run the promise chain in order', async () => {
        const validateStub = sinon
          .stub(awsCompileWebsocketsEvents, 'validate')
          .returns({
            events: [
              {
                functionName: 'first',
                websocket: {
                  route: 'echo',
                },
              },
            ],
          })

        return awsCompileWebsocketsEvents.hooks['package:compileEvents']().then(
          () => {
            expect(validateStub.calledOnce).to.be.equal(true)
            expect(compileApiStub.calledAfter(validateStub)).to.be.equal(true)
            expect(
              compileIntegrationsStub.calledAfter(compileApiStub),
            ).to.be.equal(true)
            expect(
              compileAuthorizersStub.calledAfter(compileIntegrationsStub),
            ).to.be.equal(true)
            expect(
              compilePermissionsStub.calledAfter(compileAuthorizersStub),
            ).to.be.equal(true)
            expect(
              compileRoutesStub.calledAfter(compilePermissionsStub),
            ).to.be.equal(true)
            expect(compileStageStub.calledAfter(compileRoutesStub)).to.be.equal(
              true,
            )
            expect(
              compileDeploymentStub.calledAfter(compileStageStub),
            ).to.be.equal(true)
          },
        )
      })
    })

    it('should resolve if no functions are given', () => {
      awsCompileWebsocketsEvents.serverless.service.functions = {}

      return awsCompileWebsocketsEvents.hooks['package:compileEvents']()
    })
  })
})

describe('test/unit/lib/plugins/aws/package/compile/events/websockets/index.test.js', () => {
  describe('regular configuration', () => {
    let cfTemplate
    let awsNaming
    before(async () => {
      ;({ cfTemplate, awsNaming } = await runServerless({
        fixture: 'function',
        command: 'package',

        configExt: {
          provider: {
            websocket: {
              useProviderTags: true,
            },
          },
          functions: {
            basic: {
              events: [
                {
                  websocket: '$connect',
                },
              ],
            },
          },
        },
      }))
    })

    it('should create a websocket api resource', () => {
      const websocketsApiName = awsNaming.getWebsocketsApiName()
      expect(cfTemplate.Resources.WebsocketsApi).to.deep.equal({
        Type: 'AWS::ApiGatewayV2::Api',
        Properties: {
          Name: websocketsApiName,
          RouteSelectionExpression: '$request.body.action',
          Description: 'Serverless Websockets',
          ProtocolType: 'WEBSOCKET',
        },
      })
    })

    it('should configure expected IAM', () => {
      const id = awsNaming.getRoleLogicalId()
      expect(
        cfTemplate.Resources[id].Properties.Policies[0].PolicyDocument
          .Statement,
      ).to.deep.include({
        Effect: 'Allow',
        Action: ['execute-api:ManageConnections'],
        Resource: [
          {
            'Fn::Sub': 'arn:${AWS::Partition}:execute-api:*:*:*/@connections/*',
          },
        ],
      })
    })
  })

  describe('regular configuration with tags', () => {
    let cfTemplate
    let awsNaming
    before(async () => {
      ;({ cfTemplate, awsNaming } = await runServerless({
        fixture: 'function',
        command: 'package',

        configExt: {
          provider: {
            stackTags: {
              stack_tag: 'foo',
            },
            tags: {
              tag: 'bar',
            },
            websocket: {
              useProviderTags: true,
            },
          },
          functions: {
            basic: {
              events: [
                {
                  websocket: '$connect',
                },
              ],
            },
          },
        },
      }))
    })

    it('should create a websocket api resource with tags', () => {
      const websocketsApiName = awsNaming.getWebsocketsApiName()
      expect(cfTemplate.Resources.WebsocketsApi).to.deep.equal({
        Type: 'AWS::ApiGatewayV2::Api',
        Properties: {
          Name: websocketsApiName,
          RouteSelectionExpression: '$request.body.action',
          Description: 'Serverless Websockets',
          ProtocolType: 'WEBSOCKET',
          Tags: {
            tag: 'bar',
          },
        },
      })
    })
  })

  describe('external websocket API', () => {
    let cfTemplate
    let awsNaming
    before(async () => {
      ;({ cfTemplate, awsNaming } = await runServerless({
        fixture: 'function',
        command: 'package',

        configExt: {
          provider: {
            apiGateway: {
              websocketApiId: '5ezys3sght',
            },
            iam: {
              role: 'arn:aws:iam::123456789012:role/fromProvider',
            },
          },
          functions: {
            basic: {
              events: [
                {
                  websocket: '$connect',
                },
              ],
            },
          },
        },
      }))
    })

    it('should not create a websocket api resource', () => {
      expect(cfTemplate.Resources.WebsocketsApi).to.equal(undefined)
    })

    it('should not configure IAM policies with custom roles', () => {
      const id = awsNaming.getRoleLogicalId()
      expect(cfTemplate.Resources[id]).to.equal(undefined)
    })
  })
})
